package com.et.mserver;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ReceivedBuffer {

	public final static AttributeKey MSGBUFFER_ATTRIBUTE_KEY = new AttributeKey(ReceivedBuffer.class, "message buffer key");

	public ReceivedBuffer(final int prefixLength) {

		this.prefixLength = prefixLength; // 每个消息都有消息头 (4个bytes), 其中存放这个消息的总长度

		// 消息头会读到prefixBuffer里面.
		// 当整个消息读完以后, 会调用prefixBuffer.clear重置到初始状态, 准备读下一个消息
		// 如果消息没有读完, prefixBuffer.position就始终不能为0
		this.prefixBuffer = IoBuffer.allocate(prefixLength); // 预先分配好IoBuffer来存放消息头, 4 bytes
		this.prefixBuffer.setAutoExpand(false);
		this.prefixBuffer.setAutoShrink(false);

		// 真正的数据会被存放在这个buffer里面;
		// buffer的大小由消息头决定, 其空间在收到了消息头以后分配;
		// 但是buffer有一个最大值: 2048 * 1024
		//   1) xml格式的消息的长度不能大于 2048 * 1024
		//   2) 如果是二进制的数据消息, 数据可能大于 2048 * 1024,
		//      就不得不一次次的读入 2048 * 1024 的数据, 直到整个消息读完后 (totalExceptLen == 0)
		//      才能调用 prefixBuffer.clear(), 准备开始读下一个消息的消息头
		this.buffer = null;
		this.position = 0;

		// 和prefixBuffer配合使用: 如果消息没有读完, totalExceptLen > 0, 那么也不能调用prefixBuffer.clear()
		this.totalExpectLen = 0;

		this.readNormalMsg = true; // 可能收到两种类型的消息. true: xml格式; false: binary
	}

	public byte[] getBuffer() {

		return buffer;
	}

	public BinaryMessageSegment getAsBinaryMessage() {

		final boolean lastSegment = (this.readNormalMsg || (totalExpectLen == 0));
		return new BinaryMessageSegment(buffer, lastSegment);
	}

	public boolean isNewMessage() {

		return (prefixBuffer.position() == 0);
	}

	public void setReadNormalMsg(final boolean readNormalMsg) {

		this.readNormalMsg = readNormalMsg;
	}

	public boolean getReadNormalMsg() {

		return this.readNormalMsg;
	}

	public synchronized boolean isFull() {

		return (isDataReading() && (position >= buffer.length));
	}

	private boolean isDataReading() {

		return (!prefixBuffer.hasRemaining());
	}

	public synchronized void clear() {

		if (this.readNormalMsg || (totalExpectLen == 0)) {
			prefixBuffer.clear();
			buffer = null;
		} else {
			// extend the buffer to receive more data
			final int length;
			if (totalExpectLen > MServerConstants.DEFAULT_MAX_DATA_LENGTH) {
				length = MServerConstants.DEFAULT_MAX_DATA_LENGTH;
			} else {
				length = this.totalExpectLen;
			}

			buffer = new byte[length];
		}

		position = 0;

		if (logger.isTraceEnabled()) {
			logger.trace("after clear. " + this.prefixBufferState() + " expected length: " +  + this.totalExpectLen);
		}
	}

	private String prefixBufferState() {
		return "prefix buffer position, limit has remaining: " + prefixBuffer.position() + " " + prefixBuffer.limit();
	}

	public synchronized void append(final IoBuffer in) {

		if (prefixBuffer.hasRemaining()) {

			if (logger.isTraceEnabled()) {
				logger.trace("append1. " + this.prefixBufferState());
			}

			while (prefixBuffer.hasRemaining() && in.hasRemaining()) {
				prefixBuffer.put(in.get()); // loop to get a byte from IoBuffer
			}

			if (!prefixBuffer.hasRemaining()) {
				prefixBuffer.rewind(); // perpare for read the prefix
				reset(readFieldSize(prefixBuffer)); // reset the data buffer by prefix
			}
		} else {
			if (logger.isTraceEnabled()) {
				logger.trace("append2. " + this.prefixBufferState());
			}
		}

		if (logger.isTraceEnabled()) {
			logger.trace("append3. " + this.prefixBufferState());
		}

		int canReadLen = in.remaining();
		if (canReadLen <= 0) {
			return;
		}

		final int bufRemain = this.buffer.length - this.position;
		if (canReadLen > bufRemain) {
			canReadLen = bufRemain;
		}

		in.get(buffer, position, canReadLen);

		position += canReadLen;
		totalExpectLen -= canReadLen;

		if (logger.isTraceEnabled()) {
			logger.trace("append4. buffer.position: " + position + " totalExcetpionLen: " + totalExpectLen);
		}

	}

	private int readFieldSize(final IoBuffer in) {

		int fieldSize = 0;

		switch (prefixLength) {
			case 1:
				fieldSize = in.getUnsigned();
				break;
			case 2:
				fieldSize = in.getUnsignedShort();
				break;
			case 4:
				fieldSize = in.getInt();
				break;
		}

		if (fieldSize < 0) {
			return 0;
		}

		return fieldSize;
	}

	private void reset(final int length) {

		// reset expected length
		this.totalExpectLen = length;

		// compute for the data buffer length
		final int bufferLength;
		if (this.readNormalMsg) {

			if (logger.isTraceEnabled()) {
				logger.trace("reset bytes for the text message: " + length);
			}

			if (length <= 0) {
				throw new RuntimeException("length size of message is bad");
			}

			if (length > MServerConstants.DEFAULT_MAX_DATA_LENGTH) {
				throw new RuntimeException("Message too long!");
			}

			bufferLength = length;

		} else {

			if (logger.isTraceEnabled()) {
				logger.trace("reset bytes for the binary message: " + length);
			}

			if (length <= 0) {
				throw new RuntimeException("length size of message is bad");
			}

			if (length > MServerConstants.DEFAULT_MAX_DATA_LENGTH) {
				bufferLength = MServerConstants.DEFAULT_MAX_DATA_LENGTH;
			} else {
				bufferLength = length;
			}
		}

		if (logger.isTraceEnabled()) {
			logger.trace("reset buffer length for the message: " + bufferLength);
		}

		// reset the buffer by the buffer length
		this.buffer = new byte[bufferLength];
		this.position = 0;
	}

	private final int prefixLength;
	private final IoBuffer prefixBuffer;
	private byte[] buffer;
	private int position;
	private int totalExpectLen;
	private boolean readNormalMsg;

	private static Logger logger = LoggerFactory.getLogger(ReceivedBuffer.class);
}
